package com.simple.trade.service.vm.impl;

import com.google.common.base.Preconditions;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Maps;
import com.simple.common.entity.ReqContext;
import com.simple.common.entity.ResData;
import com.simple.common.entity.constant.Payment;
import com.simple.common.entity.dto.pay.vm.PayRspDto;
import com.simple.common.entity.dto.pay.vm.VmBaseDto;
import com.simple.common.entity.po.sys.*;
import com.simple.common.entity.req.vm.pay.PayBase;
import com.simple.common.entity.vo.pay.BaseRes;
import com.simple.trade.holder.PayContext;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.time.DateFormatUtils;
import org.apache.commons.lang3.tuple.Pair;

import java.lang.reflect.ParameterizedType;
import java.sql.Time;
import java.time.LocalTime;
import java.util.*;
import java.util.function.Consumer;

/**
 * 支付相关
 *
 * @author yasud
 * @date 2020年09月30日 09:26
 * @description
 */
@Slf4j
public abstract class PayBaseService<R extends BaseRes, T extends PayRspDto> extends AbstractService {

    /**
     * 上下文
     */
    private final ThreadLocal<Map<String, Object>> CONTEXT = new InheritableThreadLocal<>();

    /**
     * 获取
     */

    /**
     * @param req
     * @return
     */
    public ResData<R> pay(PayBase req,
                          Consumer<Trade> consumer,
                          Payment.PAY_TYPE type) {
        try {
            HashMap<String, Object> map = Maps.newHashMap();
            map.put("merchant_order_no", req.getOrderNo());
            map.put("app_id", req.getAppId());
            List<Trade> tradeList = tradeApi.findByProperty_(map);
            Preconditions.checkState(CollectionUtils.isEmpty(tradeList), "duplicate order");
            log.info("商户订单{}进行{}支付", req.getOrderNo(), type.name);
            Pair<ChannelComp, Product> pair = findChannelAnProduct();
            Application application = PayContext.current().app();
            ChannelComp channel = pair.getLeft();
            Product product = pair.getRight();
            Trade trade = generateTrade(req, channel, product, application, consumer);
            trade = tradeApi.save_(trade);
            log.info("商户订单{}创建交易成功 交易流水{}", req.getOrderNo(), trade.getId());
            ResData<T> resData = forwardToChannel(getDto(trade, PayContext.current().getContext(), req, channel, product), channel, type, getTClass());
            if (resData.isOk()) {
                T rsp = resData.getData();
                Trade trade1 = rsp.getTrade();
                Preconditions.checkNotNull(trade1, "trade not null!!");
                if (trade1.getStatus() == Trade.Status.SUCCESS) {
                    trade1.setCompleteTime(new Date());
                }
                tradeApi.updateById_(trade1);
                log.info("商户订单{} 交易{}处理成功，返回结果{}", req.getOrderNo(), trade.getId(), gson.toJson(resData));
                context(ImmutableMap.of(
                        "channel", channel,
                        "product", product
                ));
                return ResData.ok(map(rsp));
            } else {
                String msg = resData.getMsg();
                log.info("商户订单{} 交易{}处理失败, 返回结果{}", req.getOrderNo(), trade.getId(), gson.toJson(resData));
                trade.setStatus(Trade.Status.FAILED);
                trade.setMsg(msg);
                trade.setChannelStatus(Trade.Status.FAILED.name());
                trade.setChannelMsg(msg);
                tradeApi.updateById_(trade);
                return ResData.error(msg);
            }
        } finally {
            CONTEXT.remove();
        }
    }

    /**
     * 上下文设置
     *
     * @param map
     */
    private void context(Map<String, Object> map) {
        CONTEXT.set(map);
    }

    /**
     * 上下文获取这的map 没法设置
     *
     * @return
     */
    protected Map<String, Object> context() {
        return CONTEXT.get();
    }

    /**
     * 获取实际类型
     *
     * @return
     */
    private Class<T> getTClass() {
        Class<T> tClass = (Class<T>) ((ParameterizedType) getClass().getGenericSuperclass()).getActualTypeArguments()[1];
        return tClass;
    }

    /**
     * 得到dto
     *
     * @return
     */
    protected abstract VmBaseDto getDto(Trade trade, ReqContext reqContext, PayBase payBase, ChannelComp channel, Product product);

    /**
     * 结果转化
     *
     * @param rsp
     * @return
     */
    protected abstract R map(T rsp);

    /**
     * 这里创建的是直连支付v1接口的交易
     *
     * @param req
     * @param channel
     * @param product
     * @return
     */
    @SneakyThrows
    protected Trade generateTrade(PayBase req,
                                  ChannelComp channel,
                                  Product product,
                                  Application application,
                                  Consumer<Trade> consumer) {
        log.info("商户订单{} 渠道{}创建订单", req.getOrderNo(), channel.getCode());
        Trade trade = new Trade();
        Merchant merchant = PayContext.current().merchant();
        Application app = PayContext.current().app();
        AppExample exp = PayContext.current().exp();
        //设置订单所属
        trade.setMerchantNo(merchant.getMerchantNo());
        trade.setMerchantName(merchant.getName());
        trade.setApplicationId(app.getId());
        trade.setApplicationName(application.getName());
        trade.setApplicationType(app.getType());
        trade.setAppId(exp.getAppId());
        trade.setAppName(exp.getName());

        trade.setMerchantOrderNo(req.getOrderNo());
        trade.setAmount(req.getAmount());
        trade.setCurrency(req.getCurrency());
        //默认情况下这里支付的金额以及币种和商户传给我的是一样的
        trade.setConvertOrderAmount(req.getAmount());
        trade.setConvertOrderCurrency(req.getCurrency());

        trade.setPayAmount(req.getAmount());
        trade.setPayAmountCurrency(req.getCurrency());

        //先判断有没有汇率，没有汇率直接跳过，
        //如果有汇率先计算汇率转化后金额然后重新设置交易币种，交易金额
        //最后判断是否有加点，如果有加点判断加点类型并重新计算汇率，重新设置交易币种，交易金额
        trade.setProductId(product.getId());
        trade.setProductName(product.getName());

        trade.setStatus(Trade.Status.ACCEPT);
        trade.setMsg(Trade.Status.ACCEPT.name());
        trade.setRefundStatus(Trade.RefundStatus.NONE);
        trade.setRefundAmount("0.00");

        trade.setChannelCode(channel.getCode());
        trade.setChannelId(channel.getId());
        trade.setChannelName(channel.getName());
        trade.setOrderDesc(req.getAsStringFromExtra("remark"));
        trade.setAsyncUrl(req.getAsyncUrl());

        ChannelComp channelComp = PayContext.current().channel();
        ChannelMerchant channelMerchant = channelComp.getChannelMerchant();
        trade.setChannelMerchantId(channelComp.getChannelMerchantId());
        trade.setChannelMerchantName(channelMerchant.getName());

        ExampleChannel expChannel = PayContext.current().expChannel();
        trade.setExpChannelId(expChannel.getId());

        trade.setYmd(DateFormatUtils.format(new Date(), "yyyy-MM-dd"));
        trade.setTime(Time.valueOf(LocalTime.now()));
        //直连没有同步跳转地址
        //直连简单的没有shipping, billing, product  的信息
        trade.setExtraInfo(objectMapper.writeValueAsString(req.getExtraInfo()));
        trade.setType(Trade.Type.NORMAL_TRADE_PAY);
        consumer.accept(trade);
        return trade;
    }
}
